﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
using Newtonsoft.Json.Linq;

namespace MailRuWrapper.Server
{
    /// <summary>
    /// Серверная сторона 
    /// </summary>
    public class ServerWrapper : BaseWrapper
    {
        /// <summary>
        /// Создает новый экземпляр
        /// </summary>
        /// <param name="serverKey">Секретный ключ</param>
        /// <param name="initParams">Словарь параметров инициализации. Удобно передавать QueryString от клиентской стороны</param>
        public ServerWrapper(string serverKey, IDictionary<String, String> initParams)
        {
            ServerKey = serverKey;
            try
            {
                AppID = Int32.Parse(initParams["app_id"]);
            }
            catch (Exception _ex)
            {
                throw new MailRuWrapperException("app_id missing", _ex);
            }

            try
            {
                SessionKey = initParams["session_key"];
            }
            catch (Exception _ex)
            {
                throw new MailRuWrapperException("session_key missing", _ex);
            }

            try
            {
                AuthenticationKey = initParams["authentication_key"];
            }
            catch (Exception _ex)
            {
                throw new MailRuWrapperException("authentication_key missing", _ex);
            }

            try
            {
                VID = initParams["vid"];
            }
            catch (Exception _ex)
            {
                throw new MailRuWrapperException("vid missing", _ex);
            }

            try
            {
                WindowID = initParams["window_id"];
            }
            catch (Exception _ex)
            {
                throw new MailRuWrapperException("window_id missing", _ex);
            }
        }

        /// <summary>
        /// Создает новый экземпляр
        /// </summary>
        /// <param name="serverKey">Секретный ключ</param>
        /// <param name="appId">Идентификатор приложения</param>
        /// <remarks>Будьте внимательны, для некоторых запросов могут понадобиться дополнительные параметры</remarks>
        public ServerWrapper(string serverKey, int appId)
        {
            ServerKey = serverKey;
            AppID = appId;
        }

        #region Api

        /// <summary>
        /// Функции для работы с уведомлениями
        /// </summary>
        public Notifications Notifications
        {
            get
            {
                return new Notifications(this);
            }
        }

        /// <summary>
        /// Функции для работы с виджетом приложения
        /// </summary>
        public Widget Widget
        {
            get
            {
                return new Widget(this);
            }
        }

        #endregion

        /// <summary>
        /// Секретный серверный ключ
        /// </summary>
        public String ServerKey { get; private set; }

        /// <summary>
        /// Стандартные поля запроса app_id method secure sig
        /// </summary>
        /// <param name="method"></param>
        /// <param name="customParams"></param>
        /// <returns></returns>
        internal override string GetRequestString(FuncType method, params string[] customParams)
        {
            if (AppID <= 0)
                throw new ArgumentNullException("AppID");

            var _params = new List<String>
                              {
                                  method.ToRequestParam("method"),
                                  AppID.ToRequestParam("app_id"),
                                  Format.ToRequestParam("format"),
                                  "secure=1"
                              };

            _params.AddRange(customParams.Where(item => item != ""));
            _params.Sort();
            var _uri = ApiUri + "?" + _params.Aggregate((current, next) => current + "&" + next) + "&sig=" +
                       _params.ComputeSIG(ServerKey);

            Debug.WriteLine(_uri);

            return Uri.EscapeUriString(_uri);
        }

        internal override object ParsResponse(string response, FuncType method, string request)
        {
            if (string.IsNullOrEmpty(response))
                throw new ArgumentNullException("response");

            switch (Format)
            {
                case ApiFormat.Json:

                    #region Json

                    try
                    {
                        var _jObject = JObject.Parse(response);
                        if (_jObject.Value<JObject>("error") != null)
                        {
                            throw new MailRuWrapperRequestException(
                                _jObject.Value<JObject>("error").Value<int>("error_code"),
                                _jObject.Value<JObject>("error").Value<String>("error_msg"),
                                request);
                        }
                    }
                    catch (MailRuWrapperRequestException _ex)
                    {
                        InvokeLog("Response contain error:" + _ex.Message, LogLevel.Api);
                        throw _ex;
                    }
                    catch
                    {
                    }


                    switch (method)
                    {
                        case FuncType.FriendsGetAppUsers:
                        case FuncType.FriendsGet:
                        case FuncType.UsersGetInfo:
                            var _users = JArray.Parse(response);
                            return _users.Children().Select(User.Parse).ToArray();
                        case FuncType.UsersIsAppUser:
                            var _bool = JObject.Parse(response);
                            return _bool.Value<string>("isAppUser") == "1";
                        case FuncType.UsersHasAppPermission:
                            _bool = JObject.Parse(response);
                            return _bool.First.Value<JProperty>().Value.Value<String>() == "1";
                        case FuncType.PhotosGetAlbums:
                            var _pAlbums = JArray.Parse(response);
                            return _pAlbums.Children().Select(PhotoAlbum.Parse).ToArray();
                        case FuncType.PhotosGet:
                            var _photos = JArray.Parse(response);
                            return _photos.Children().Select(Photo.Parse).ToArray();
                        case FuncType.AudiosGet:
                            var _audios = JArray.Parse(response);
                            return _audios.Children().Select(Audio.Parse).ToArray();
                        case FuncType.NotificationsSend:
                            var _send = JArray.Parse(response);
                            return _send.Values<String>().ToArray();
                        case FuncType.StreamPublish:
                            _bool = JObject.Parse(response);
                            return _bool.Value<string>("publish") == "1";
                        case FuncType.PaymentsOpenDialog:
                            _bool = JObject.Parse(response);
                            return _bool.Value<string>("openDialog") == "1";
                        case FuncType.WidgetSet:
                            _bool = JObject.Parse(response);
                            return _bool.Value<string>("set") == "1";
                    }
                    InvokeLog("NotImplemented " + method, LogLevel.Critical);
                    throw new NotImplementedException(method.ToString());

                    #endregion

                case ApiFormat.Xml:

                    #region Xml

                    var _xml = XDocument.Parse(response);

                    if (_xml.Root != null)
                    {
                        if (_xml.Root.Name == "error")
                        {
                            InvokeLog("Response contain error:" + _xml.Root.Element("error_msg").Value, LogLevel.Api);
                            throw new MailRuWrapperRequestException(
                                _xml.Root.Element("error_code").Value.ToInt(),
                                _xml.Root.Element("error_msg").Value,
                                request);
                        }
                        switch (method)
                        {
                            case FuncType.FriendsGetAppUsers:
                            case FuncType.FriendsGet:
                            case FuncType.UsersGetInfo:
                                return _xml.Root.Elements().Select(User.Parse).ToArray();
                            case FuncType.UsersIsAppUser:
                            case FuncType.UsersHasAppPermission:
                            case FuncType.PaymentsOpenDialog:
                            case FuncType.StreamPublish:
                            case FuncType.AudiosLinkAudio:
                            case FuncType.WidgetSet:
                                return _xml.Root.Elements().First().Value.ToBoolean();
                            case FuncType.PhotosGetAlbums:
                                return _xml.Root.Elements().Select(PhotoAlbum.Parse).ToArray();
                            case FuncType.PhotosGet:
                                return _xml.Root.Elements().Select(Photo.Parse).ToArray();
                            case FuncType.AudiosGet:
                                return _xml.Root.Elements().Select(Audio.Parse).ToArray();
                            case FuncType.NotificationsSend:
                                return _xml.Root.Elements().Select(e => e.Value).ToArray();
                        }
                    }

                    InvokeLog("NotImplemented " + method, LogLevel.Critical);
                    throw new NotImplementedException(method.ToString());

                    #endregion

            }

            InvokeLog("NotImplemented " + method, LogLevel.Critical);
            throw new NotImplementedException(method.ToString());
        }


        /// <summary>
        /// Проверить ключ аутендификации пользователя
        /// </summary>
        /// <returns></returns>
        public bool CheckAuthenticationKey()
        {
            if (string.IsNullOrEmpty(AuthenticationKey))
                throw new ArgumentNullException("AuthenticationKey");
            if (AppID <= 0)
                throw new ArgumentNullException("AppID");
            if (string.IsNullOrEmpty(VID))
                throw new ArgumentNullException("VID");
            if (string.IsNullOrEmpty(ServerKey))
                throw new ArgumentNullException("ServerKey");

            return AuthenticationKey == (AppID + "_" + VID + "_" + ServerKey).MD5Hash();
        }

    }

    /// <summary>
    /// Функции для работы с уведомлениями
    /// </summary>
    public sealed class Notifications
    {
        private readonly BaseWrapper _baseWrapper;

        internal Notifications(BaseWrapper baseWrapper)
        {
            _baseWrapper = baseWrapper;
        }

        #region Send

        /// <summary>
        /// Отправляет уведомления пользователям, функция может быть вызвана только с сервера 
        /// приложения; Пользователь не может получать более одного уведомления от приложения 
        /// в 24 часа.
        /// </summary>
        /// <param name="uids">Идентификаторы пользователей, которым будет отправлено уведомление (максимум 200 пользователей)</param>
        /// <param name="text">Текст уведомления</param>
        public string[] Send(string text, params string[] uids)
        {
            if (text.Length > 255)
                _baseWrapper.ThrowValidation("text не может быть более 255 символов");

            if (uids.Length > 200)
                _baseWrapper.ThrowValidation("uids может содеражать максимум 200 идентификаторов");

            var _request = _baseWrapper.GetRequestString(FuncType.NotificationsSend,
                                            uids.ToRequestParam("uids"),
                                            text.ToRequestParam("text"));

            var _result = _baseWrapper.GetResponse(_request);

            return _baseWrapper.ParsResponse(_result, FuncType.NotificationsSend, _request) as string[];
        }

        #region Async Send

        /// <summary>
        /// Отправляет уведомления пользователям, функция может быть вызвана только с сервера 
        /// приложения; Пользователь не может получать более одного уведомления от приложения 
        /// в 24 часа.
        /// </summary>
        /// <param name="uids">Идентификаторы пользователей, которым будет отправлено уведомление (максимум 200 пользователей)</param>
        /// <param name="text">Текст уведомления</param>
        public IAsyncResult BeginSend(AsyncCallback callback, string[] uids, string text, object state = null)
        {
            if (text.Length > 255)
                _baseWrapper.ThrowValidation("text не может быть более 255 символов");

            if (uids.Length > 200)
                _baseWrapper.ThrowValidation("uids может содеражать максимум 200 идентификаторов");

            var _request = _baseWrapper.GetRequestString(FuncType.NotificationsSend,
                                            uids.ToRequestParam("uids"),
                                            text.ToRequestParam("text"));
            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public string[] EndSend(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return _baseWrapper.ParsResponse(_result.Response, FuncType.NotificationsSend, _result.Request) as string[];
        }

        #endregion

        #endregion

    }

    /// <summary>
    /// Функции для работы с виджетом приложения
    /// </summary>
    public sealed class Widget
    {
        private readonly BaseWrapper _baseWrapper;

        internal Widget(BaseWrapper baseWrapper)
        {
            _baseWrapper = baseWrapper;
        }

        #region Set

        /// <summary>
        /// Публикует виджет приложения на профиле пользователя в "Моем мире". 
        /// Для публикации виджета пользователь должен дать разрешение "widget" для приложения. 
        /// На количество публикаций виджета в день ограничения не накладываются, поэтому виджет приложения 
        /// удобно использовать для отражения статуса игрока по какому-то событию, даже без присутствия пользователя он-лайн.
        /// </summary>
        /// <param name="uid">Идентификатор пользователя, которому устанавливается виджет.</param>
        /// <param name="html">HTML код виджета, который будет размещен на странице пользователя.</param>
        public bool Set(string uid, string html)
        {
            if (html.Length > 3000)
                _baseWrapper.ThrowValidation("Максимальный объем кода - 3000 символов");
            
            var _request = _baseWrapper.GetRequestString(FuncType.WidgetSet,
                                            uid.ToRequestParam("uid"),
                                            html.Escape().ToRequestParam("html"));

            var _result = _baseWrapper.GetResponse(_request);

            return (bool) _baseWrapper.ParsResponse(_result, FuncType.WidgetSet, _request);
        }

        #region Async Send

        /// <summary>
        /// Публикует виджет приложения на профиле пользователя в "Моем мире". 
        /// Для публикации виджета пользователь должен дать разрешение "widget" для приложения. 
        /// На количество публикаций виджета в день ограничения не накладываются, поэтому виджет приложения 
        /// удобно использовать для отражения статуса игрока по какому-то событию, даже без присутствия пользователя он-лайн.
        /// </summary>
        /// <param name="uid">Идентификатор пользователя, которому устанавливается виджет.</param>
        /// <param name="html">HTML код виджета, который будет размещен на странице пользователя.</param>
        public IAsyncResult BeginSet(AsyncCallback callback, string uid, string html, object state = null)
        {
            if (html.Length > 3000)
                _baseWrapper.ThrowValidation("Максимальный объем кода - 3000 символов");

            var _request = _baseWrapper.GetRequestString(FuncType.WidgetSet,
                                            uid.ToRequestParam("uid"),
                                            html.Escape().ToRequestParam("html"));
            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public bool EndSet(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return (bool) _baseWrapper.ParsResponse(_result.Response, FuncType.WidgetSet, _result.Request);
        }

        #endregion

        #endregion
    }
}